/**
 * Copyright 2009 The University of North Carolina at Chapel Hill
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package irodsfedora.storage.lowlevel;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import edu.sdsc.grid.io.FileFactory;
import edu.sdsc.grid.io.GeneralMetaData;
import edu.sdsc.grid.io.MetaDataCondition;
import edu.sdsc.grid.io.MetaDataRecordList;
import edu.sdsc.grid.io.MetaDataSelect;
import edu.sdsc.grid.io.MetaDataSet;
import edu.sdsc.grid.io.irods.IRODSAccount;
import edu.sdsc.grid.io.irods.IRODSFile;
import edu.sdsc.grid.io.irods.IRODSFileSystem;
import fedora.server.errors.LowlevelStorageException;
import fedora.server.errors.LowlevelStorageInconsistencyException;
import fedora.server.storage.lowlevel.DBPathRegistry;
import fedora.server.storage.lowlevel.PathAlgorithm;

/**
 * This is a registry implementation based on Bill Niebel's DBPathRegistry for Fedora lowlevel storage.  This
 * implementation takes advantage of the iRODS iCAT database lookups to efficiently list files and perform the four
 * auditing operations specified in the parent class.  At some point it may make sense to divorce the iRODS implementation
 * from the Fedora base classes, but for now this works.  I suspect that a completely separate module implementation
 * would require some abstract classes to become interfaces in Fedora.
 *
 * Note: This registry relies on standard configuration information for the parent class and stores it's lookup table
 * in the Fedora database schema.
 *
 * Note: Another registry idea is to store PID information in the iCAT database and simply use that as the
 * Fedora registry.  While this is a clean way of normalizing path registry data, it will introduce some latency.  It
 * also presents some big challenges for rebuilds and audits as these fields would need to be reset individually in the
 * current iRODS API.
 *
 * @author Gregory Jansen
 */
public class IrodsDBPathRegistry extends DBPathRegistry {

    private IRODSAccount account;

    /** Logger for this class. */
    private static final Logger LOG = Logger.getLogger(IrodsDBPathRegistry.class.getName());

    public IrodsDBPathRegistry(IRODSAccount account, Map configuration) {
	super(configuration);
	this.account = account;
    }

    private final IRODSFileSystem getFileSystem() throws IOException {
	LOG.debug("Creating an IRODS file system");
	IRODSFileSystem result = null;
	try {
	    result = (IRODSFileSystem) FileFactory.newFileSystem(account);
	} catch (Exception e) {
	    throw new IOException("connect_to_irods() failed." + e);
	}
	return result;
    }

    /*
     * Perform the selected operation on the entire tree of files
     *
     * @see
     * fedora.server.storage.lowlevel.PathRegistry#traverseFiles(java.lang.String
     * [], int, boolean, int)
     */
    @Override
    public void traverseFiles(String[] storeBases, int operation, boolean stopOnError, int report)
		    throws LowlevelStorageException {
	for (int i = 0; i < storeBases.length; i++) {
	    IRODSFileSystem sys = null;
	    try {
		sys = this.getFileSystem();
	    } catch (IOException e) {
		throw new LowlevelStorageException(true, "Could not obtain IRODS connection", e);
	    }
	    IRODSFile base = new IRODSFile(sys, storeBases[i]);
	    if (!base.exists() || !base.isDirectory()) {
		throw new LowlevelStorageException(true, "Base directory could not be found in IRODS: " + storeBases[i]);
	    }
	    Set<String[]> files = getAllIrodsFilePaths(base);
	    for (String[] file : files) {
		String dir = file[1];
		String filename = file[0];
		String pid = PathAlgorithm.decode(filename);
		String path = dir + "/" + filename;
		if (pid == null) {
		    if (report != NO_REPORT) {
			LOG.error("unexpected file at [" + path + "]");
		    }
		    if (stopOnError) {
			throw new LowlevelStorageException(true, "unexpected file traversing object store at [" + path
					+ "]");
		    }
		} else {
		    switch (operation) {
		    case REPORT_FILES: {
			if (report == FULL_REPORT) {
			    LOG.info("file [" + path + "] would have pid [" + pid + "]");
			}
			break;
		    }
		    case REBUILD: {
			put(pid, path);
			if (report == FULL_REPORT) {
			    LOG.info("added to registry: [" + pid + "] ==> [" + path + "]");
			}
			break;
		    }
		    case AUDIT_FILES: {
			String rpath = null;
			try {
			    rpath = get(pid);
			} catch (LowlevelStorageException e) {
			}
			boolean matches = rpath.equals(path);
			if (report == FULL_REPORT || !matches) {
			    LOG.info((matches ? "" : "ERROR: ")
					    + "["
					    + path
					    + "] "
					    + (matches ? "" : "NOT ")
					    + "in registry"
					    + (matches ? "" : "; pid [" + pid + "] instead registered as ["
							    + (rpath == null ? "[OBJECT NOT IN STORE]" : rpath) + "]"));
			}
		    }
		    }
		}
	    }
	}
    }

    private Set<String[]> getAllIrodsFilePaths(IRODSFile base) throws LowlevelStorageException {
	Set<String[]> result = new HashSet<String[]>();
	String basepath;
	try {
	    basepath = base.getCanonicalPath();
	} catch (IOException e) {
	    throw new LowlevelStorageException(true, "Could not obtain canonical path for base", e);
	}
	MetaDataSelect selects[] = { MetaDataSet.newSelection(GeneralMetaData.FILE_NAME),
			MetaDataSet.newSelection(GeneralMetaData.DIRECTORY_NAME), };
	MetaDataCondition con[] = { MetaDataSet.newCondition(GeneralMetaData.DIRECTORY_NAME, MetaDataCondition.LIKE,
			basepath + "%") };
	try {
	    MetaDataRecordList[] rl = base.getFileSystem().query(con, selects);
	    rl = MetaDataRecordList.getAllResults(rl);
	    for (MetaDataRecordList r : rl) {
		String[] f = new String[2];
		f[0] = r.getStringValue(0);
		f[1] = r.getStringValue(1);
		result.add(f);
	    }
	} catch (NullPointerException e) {
	    throw new LowlevelStorageException(true, "file system was null", e);
	} catch (IOException e) {
	    throw new LowlevelStorageException(true, "Could not query file system for descendant files", e);
	}
	return result;
    }

    protected Enumeration<String> getKeys() throws LowlevelStorageInconsistencyException, LowlevelStorageException {
	return keys();
    }
}
